/******************************************************************************
 *
 * ngcap - network sniffer using raw sockets
 *
 * Author:        cr
 * E-mail:        cr.at.netgarage.org
 * WWW:           http://www.netgarage.org
 * Copyright (C): 2009 by cr
 *
 * This file is part of ngcap.
 *
 * ngcap is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * ngcap is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with ngcap.  If not, see <http://www.gnu.org/licenses/>.
 *
 *****************************************************************************/

#include "frame.hpp"
#include <string>
#include <sstream>

PayloadFrame::PayloadFrame(unsigned char* d, unsigned short s) : data(d), size(s), minlength(0)
    {}

std::string PayloadFrame::toString() {
    std::string tmp="Payload (\"";
    int count=1;
    unsigned short data_size=size;
    while(data_size>0){
        if(!minlength){
            if(isprint(*data))
                tmp+=*data;
            else
                tmp+=".";
            data++;
        } else {
            if(isprint(*data))
                count++;
            else
                if(count>=minlength){
                    while(count>0){
                        tmp+=*(data-count);
                        count--;
                    }
                    tmp+='\n';
                } else
                    count=0;
            data++;
        }
        data_size--;
    }
    tmp+="\")";
    return tmp;
}

unsigned short PayloadFrame::getSize() const {
  return size;
}

Frame* PayloadFrame::getNext() {
  return 0;
}


IcmpFrame::IcmpFrame(unsigned char* data) {
        header=reinterpret_cast<icmp_hdr*>(data);
}

std::string IcmpFrame::toString() {
    std::stringstream tmpstr;
    tmpstr<<"Icmp (";
    tmpstr<<"type=\""<<getICMPType(header->type);
    tmpstr<<"\", code=\""<<header->code;
    tmpstr<<"\", check=\""<<ntohs(header->checksum)<<"\"";
    if(header->type==ICMP_ECHO){
        tmpstr<<", id=\""<<ntohs(header->un.echo.id);
        tmpstr<<"\", seq=\""<<ntohs(header->un.echo.sequence)<<"\"";
    }
    tmpstr<<")";
    return tmpstr.str();
}

Frame* IcmpFrame::getNext() {
    return 0;
}

std::string IcmpFrame::getICMPType(unsigned char type)
{
    switch(type) {
        case ICMP_ECHOREPLY: return "ICMP_ECHOREPLY";
        case ICMP_DEST_UNREACH: return "ICMP_DEST_UNREACH";
        case ICMP_SOURCE_QUENCH: return "ICMP_SOURCE_QUENCH";
        case ICMP_REDIRECT: return "ICMP_REDIRECT";
        case ICMP_ECHO: return "ICMP_ECHO";
        case ICMP_TIME_EXCEEDED: return "ICMP_TIME_EXCEEDED";
        case ICMP_PARAMETERPROB: return "ICMP_PARAMETERPROB";
        case ICMP_TIMESTAMP: return "ICMP_TIMESTAMP";
        case ICMP_TIMESTAMPREPLY: return "ICMP_TIMESTAMPREPLY";
        case ICMP_INFO_REQUEST: return "ICMP_INFO_REQUEST";
        case ICMP_INFO_REPLY: return "ICMP_INFO_REPLY";
        case ICMP_ADDRESS: return "ICMP_ADDRESS";
        case ICMP_ADDRESSREPLY: return "ICMP_ADDRESSREPLY";
        default: return "unknown icmp type";
    }
}

UdpFrame::UdpFrame(unsigned char* d) : data(d) {
    header=reinterpret_cast<udp_hdr*>(data);
    payloadsize=ntohs(header->len)-udp_size;
}


std::string UdpFrame::toString() {
    std::stringstream tmpstr;
    tmpstr<<"UDP (";
    tmpstr<<"sport=\""<<ntohs(header->source);
    tmpstr<<"\", dport=\""<<ntohs(header->dest);
    tmpstr<<"\", len=\""<<header->len;
    tmpstr<<"\", check=\""<<header->check;
    tmpstr<<"\")";
    return tmpstr.str();
}


Frame* UdpFrame::getNext() {
    return 0; 
}


TcpFrame::TcpFrame(unsigned char* d, unsigned short tot_len) : data(d) {
    header=reinterpret_cast<tcp_hdr*>(data);
    size=header->doff*4;
    payloadsize=tot_len-size;
}

std::string TcpFrame::toString() {
    std::stringstream tmpstr;
    tmpstr<<"TCP (";
    tmpstr<<"sport=\""<<ntohs(header->source);
    tmpstr<<"\", dport=\""<<ntohs(header->dest);
    tmpstr<<"\", seq=\""<<ntohl(header->seq);
    tmpstr<<"\", ack=\""<<ntohl(header->ack_seq);
    tmpstr<<"\", win=\""<<ntohs(header->window);
    tmpstr<<"\", flags=\"";
    if(header->syn)tmpstr<<" syn ";
    if(header->ack)tmpstr<<" ack ";
    if(header->rst)tmpstr<<" rst ";
    if(header->fin)tmpstr<<" fin ";
    if(header->psh)tmpstr<<" psh ";
    if(header->urg)tmpstr<<" urg ";
    tmpstr<<"\")";
    return tmpstr.str();
}


unsigned short TcpFrame::getSize() const { 
    return size; 
}


Frame* TcpFrame::getNext() {
    return new PayloadFrame(data+size, payloadsize);
}

ArpFrame::ArpFrame(unsigned char* data) {
    header=reinterpret_cast<arp_packet*>(data);
}

std::string ArpFrame::toString() {
    std::stringstream tmpstr;
    in_addr tmpaddr;
    tmpstr<<"Arp (";
    tmpstr<<"op=\""<<getOpCode(ntohs(header->ea_hdr.ar_op));
    tmpstr<<"\", src=\"";
        for(int i=0;i<6;++i){
            tmpstr<<std::hex<<static_cast<int>(header->arp_sha[i]);
            if(i!=5)tmpstr<<":";
        }
    tmpstr<<" dst=\"";
        for(int i=0;i<6;++i){
            tmpstr<<std::hex<<static_cast<int>(header->arp_tha[i]);
            if(i!=5)tmpstr<<":";
        }
    tmpaddr.s_addr=*(reinterpret_cast<in_addr_t*>(&(header->arp_spa[0])));
    tmpstr<<"\", src=\""<<inet_ntoa(tmpaddr)<<"\", dst=\"";
    tmpaddr.s_addr=*(reinterpret_cast<in_addr_t*>(&(header->arp_tpa[0])));
    tmpstr<<inet_ntoa(tmpaddr)<<"\")";
     return tmpstr.str();
}


Frame* ArpFrame::getNext() { 
    return 0; 
}

std::string ArpFrame::getOpCode(unsigned short int opcode) {
    switch(opcode)
    {
        case ARPOP_REQUEST: return "ARPOP_REQUEST";
        case ARPOP_REPLY: return "ARPOP_REPLY";
        case ARPOP_RREQUEST: return "ARPOP_RREQUEST";
        case ARPOP_RREPLY: return "ARPOP_RREPLY";
        case ARPOP_InREQUEST: return "ARPOP_InREQUEST";
        case ARPOP_InREPLY: return "ARPOP_InREPLY";
        case ARPOP_NAK: return "ARPOP_NAK";
        default: return "unknown arp op code";
    }
}

IpFrame::IpFrame(unsigned char* d) : data(d) {
    header=reinterpret_cast<ip_hdr*>(data);
    protocol=header->protocol;
    size=header->ihl*4;
    packetsize=ntohs(header->tot_len);
}

std::string IpFrame::toString() {
    std::stringstream tmpstr;
    in_addr tmpaddr;
    tmpstr<<"IP (";
    tmpaddr.s_addr=header->saddr;
    tmpstr<<"src=\""<<inet_ntoa(tmpaddr);
    tmpaddr.s_addr=header->daddr;
    tmpstr<<"\", dst=\""<<inet_ntoa(tmpaddr);
    tmpstr<<"\", pro=\""<<static_cast<int>(header->protocol)<<"\", ";
    tmpstr<<"ttl=\""<<static_cast<int>(header->ttl)<<"\", ";
    tmpstr<<"tos=\""<<static_cast<int>(header->tos)<<"\", ";
    tmpstr<<"ver=\""<<static_cast<int>(header->version)<<"\", ";
    tmpstr<<"id=\""<<ntohs(header->id)<<"\")";
    return tmpstr.str();
}

unsigned short IpFrame::getSize() const {
    return size; 
}

Frame* IpFrame::getNext() {
    switch(header->protocol) {
        case IPPROTO_TCP: return new TcpFrame(data+size, packetsize);break;
        case IPPROTO_UDP: return new UdpFrame(data+size);break;
        case IPPROTO_ICMP: return new IcmpFrame(data+size);break;
        default: return 0;
    }
}


EthernetFrame::EthernetFrame(unsigned char* d) : data(d) {
    header=reinterpret_cast<eth_hdr*>(data);
    protocol=htons(header->h_proto);
}


std::string EthernetFrame::toString() {
    std::stringstream tmpstr;
    tmpstr<<"Ethernet (";
    tmpstr<<"dst=\"";
    for(int i=0;i<6;++i){
        tmpstr<<std::hex<<static_cast<int>(header->h_dest[i]);
            if(i!=5)tmpstr<<":";
    }
    tmpstr<<"\", src=\"";
    for(int i=0;i<6;++i){
        tmpstr<<std::hex<<static_cast<int>(header->h_source[i]);
        if(i!=5)tmpstr<<":";
    }
    tmpstr<<"\")";
    return tmpstr.str();
}


Frame* EthernetFrame::getNext() {
    switch(protocol)
        {
        case ETHERTYPE_IP: return new IpFrame(data+ethernet_size);break;
        case ETHERTYPE_ARP: return new ArpFrame(data+ethernet_size);break;
        default: return 0;
        }
}


bool EthernetFrame::matchFilter(PacketFilter* pf) {
    if(pf->getEthProtocol()==protocol)
        return true;
    return false;
}

unsigned short EthernetFrame::getProtocol() const { 
    return protocol; 
}

eth_hdr* EthernetFrame::getHeader() const { 
    return header; 
}

